﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using DiscreteEventModel;
using DiscreteEventModel.Common;

namespace SpikingNeuralNetwork {

    using Model;

    public partial class Neuron {

        public class Spike : DiscreteEvent<NeuralNetwork.NeuralState, NeuralNetwork> {

            public readonly double Potential;
            public readonly Neuron SourceNeuron;

            protected override void OnDispose(NeuralNetwork context) { }

            public Spike(DiscreteEvent<NeuralNetwork.NeuralState, NeuralNetwork>.Runtime runtime, double timing, double potential, Neuron sourceNeuron)
                : base(runtime, timing) {
                this.Potential = potential;
                this.SourceNeuron = sourceNeuron;
            }

            protected override IEnumerable<NeuralNetwork.NeuralState> GetTargets(DiscreteEvent<NeuralNetwork.NeuralState, NeuralNetwork>.Runtime runtime) {
                this.SourceNeuron.Synchronize(runtime);
                foreach(KeyValuePair<int, double> outputSynapticWeight in this.SourceNeuron.OutputSynapticWeights) {
                    NeuralNetwork.NeuralState outputState;
                    if(runtime.Context.TryGetState(outputSynapticWeight.Key, out outputState)) {
                        yield return outputState;
                    } else {
                        yield return new Neuron(runtime, outputSynapticWeight.Key);
                    }
                }
            }

            protected override DiscreteTime OnCommit(DiscreteEvent<NeuralNetwork.NeuralState, NeuralNetwork>.Runtime runtime, NeuralNetwork.NeuralState target) {
                Neuron targetNeuron = target as Neuron;
                #region Weaken synaptic weight
                double synapticWeight = this.WeakenSynapticWeight(targetNeuron, this.SourceNeuron.OutputSynapticWeights[targetNeuron.ID]);
                if(synapticWeight > 0) {
                    this.SourceNeuron.OutputSynapticWeights[targetNeuron.ID] = synapticWeight;
                } else {
                    this.SourceNeuron.OutputSynapticWeights.Remove(targetNeuron.ID);
                }
                #endregion

                if(targetNeuron.MembranePotential < targetNeuron.FiringThreshold) {
                    targetNeuron.MembranePotential += this.Potential;
                    if(targetNeuron.MembranePotential < targetNeuron.FiringThreshold) {
                        return DiscreteTime.Infinity;
                    }
                    targetNeuron.MembranePotential = targetNeuron.FiringThreshold;
                    return targetNeuron.Time;
                }
                return targetNeuron.CommitTime;
            }

            public double WeakenSynapticWeight(Neuron targetNeuron, double currentWeight) {
                return currentWeight;
            }

            public double StrengthenSynapticWeight(Neuron targetNeuron, double currentWeight) {
                return currentWeight;
            }
        }
    }
}
